/*
 * @Author: hongbin
 * @Date: 2022-09-04 11:43:20
 * @LastEditors: hongbin
 * @LastEditTime: 2023-04-04 11:25:41
 * @Description: three操作核心逻辑
 */
import THREE, { ejectorParams } from ".";
import { animationControl, AnimationPlayer, dirType, getSize, nearCheck, scaleToTarget, uphillCheck } from "./helper";
import { stats } from "./helper/stats";
import HControls from "./myControls";
import {
    acterWrap,
    acterWrapTemplate,
    cameraWrap,
    hemisphereLight,
    initScene,
    initWebGLRenderer,
    intGlobalVariable,
    renderControl,
    scene,
} from "./object";
import { parseModel } from "./utils/worker";
import { directionCheck, fallCollide, upCollide } from "./utils/RayControl";
import { loadInfoControls } from "./helper/loadInfoControls";
import { ldMap } from "./loadModels";
import { clearChildren } from "./helper/clearChildren";
import { partsControl } from "./PartsControl";
import { otherUserList } from "./helper/OtherUser";
import { CanRun } from "../helper";

let controls: ReturnType<typeof HControls>;
const objects: Array<THREE.Object3D> = [];

let rightDistance: number;
let forwardDistance: number;
/**
 * 正在上的坡的斜度
 */
let hillAngle = 1;
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
/**
 * iframe id
 */
let animationFrame: number;
/**
 * 按下shift键加速
 */
let pressShift = false;
let magnification = 13;
let canJump = false;
let prevTime = 0;
const direction = new THREE.Vector3();
const velocity = new THREE.Vector3();
/**
 * 跳跃高度
 */
const JumpHeight = 120;
/**
 * 第一次按下键盘
 */
let enterKey = false;
/**
 * 移动中
 */
let isMoving = false;
let acterHeight = 0;
let isUphill = false;
export const acterPlayer = AnimationPlayer();

let myWorker: Worker;
//TODO 调整到一个单独的文件中 以注入的方式回调响应
const initWebWorker = () => {
    myWorker = new Worker(new URL("../meta/worker/script.ts", import.meta.url));
    myWorker.onmessage = function (e) {
        const { work_type } = e.data;
        switch (work_type) {
            case "calculate":
                hemisphereLight.intensity = e.data.intensity;

                break;
            case "parseModel":
                // console.log("接收到解析的模型结构:", e.data);
                const object = parseModel(e.data);

                switch (e.data.modelName) {
                    case "acter.glb":
                        object.name = "actor";
                        //@ts-ignore
                        window.actor = object;
                        acterWrap.add(object);
                        acterWrapTemplate.add(parseModel(e.data));
                        partsControl.wear(object);
                        break;

                    // case "rucksack.glb":
                    //     handleParseRucksack(HOME.objects, object);
                    //     break;
                    default:
                        throw new Error("未设置的接收程序的模型");
                }
                break;
            case "model_load_start":
                loadInfoControls.addInfo("Web Worker 初始化", "完成");
                break;
            case "model_load":
                loadInfoControls.addInfo("Web Worker 模型加载", e.data.progress);
                break;
            case "model_load_complete":
                loadInfoControls.addInfo("Web Worker 加载", "完成");
                break;
        }
    };
    myWorker.onerror = (err) => {
        console.error("work出错：", err, err.message);
    };

    myWorker.postMessage({ work_type: "test_connect" });
    //在worker中加载人物模型
    webWorkerParse("acter.glb");
    init.clearEventArr.push(() => {
        myWorker.terminate();
    });
};

/**
 * 使用web worker解析模型
 */
export function webWorkerParse(modelName: string[] | string) {
    if (!myWorker) throw new Error("web worker 不存在");

    if (Array.isArray(modelName)) {
        modelName.forEach((name) => {
            myWorker.postMessage({ work_type: "modelParse", name });
        });
    } else myWorker.postMessage({ work_type: "modelParse", name: modelName });
}

/**
 * 初始化镜头控制器
 * 指针锁定控制器 https://threejs.org/docs/index.html?q=cont#examples/zh/controls/PointerLockControls
 * 锁定鼠标api https://developer.mozilla.org/zh-CN/docs/Web/API/Pointer_Lock_API
 */
function initControls() {
    controls = HControls(cameraWrap, document.body, acterWrap);

    const handleStart = (e: MouseEvent) => {
        const clickDom = e.target as HTMLElement;
        if (clickDom.nodeName === "CANVAS") {
            if (
                clickDom.nodeName === "CANVAS" &&
                clickDom.getAttribute("data-engine") &&
                clickDom.getAttribute("data-engine")?.indexOf("three.js") != -1
            ) {
                controls.lock();
                acterHeight = getSize(acterWrap).y;
                init.enter();
            }
        }
    };
    document.addEventListener("click", handleStart);

    initKeyPressListen();

    init.clearEventArr.push(() => {
        controls.removeEventListener();
        document.removeEventListener("click", handleStart);
    });

    controls.addEventListener("lock", function () {
        //由于指针锁定控制器在释放鼠标后停止计时 下次进入会积压太长时间 导致delta过大相关变化直接影响 比如直接向下位置数千高度导致人物在空中直接到地下
        prevTime = performance.now();

        myWorker.postMessage({
            work_type: "start",
        });
        animate();
        renderControl.stop();
        window.dispatchEvent(new CustomEvent("startrender"));
    });

    controls.addEventListener("unlock", function () {
        cancelAnimationFrame(animationFrame);
        //解除锁定后 只渲染 不计算碰撞
        renderControl.everyFrame();
    });

    /**
     * 开发阶段 点击canvas触发监听 暂不需要下方回调
     */
    return {
        /**
         * 锁定鼠标 控制器接管
         */
        lockMouse: () => {
            controls.lock();
            acterHeight = getSize(acterWrap).y;
        },

        unlockCallback: (callback: () => void) => {
            controls.addEventListener("unlock", function () {
                cancelAnimationFrame(animationFrame);
                window.dispatchEvent(new CustomEvent("stoprender"));
                callback();
            });
        },
    };
}

/**
 * 按键监听 前后左右跳 等操作
 */
function initKeyPressListen() {
    const handleDown = () => {
        isMoving = true;
    };

    const onKeyDown = function (event: { code: any }) {
        if (CanRun.inputting) return;
        switch (event.code) {
            case "ArrowUp":
            case "KeyW":
                moveForward = true;
                handleDown();
                break;

            case "ArrowLeft":
            case "KeyA":
                moveLeft = true;
                handleDown();
                break;

            case "ArrowDown":
            case "KeyS":
                moveBackward = true;
                handleDown();
                break;

            case "ArrowRight":
            case "KeyD":
                moveRight = true;
                handleDown();
                break;
            case "ShiftLeft":
            case "ShiftRIGHT":
                pressShift = true;
                handleDown();
                break;
            case "Space":
                //可以跳的时候跳 y轴增加 开始跳跃时将可以跳跃状态设置为false
                if (canJump) velocity.y += JumpHeight;
                canJump = ejectorParams.isCanJump();
                break;
        }
    };

    const onKeyUp = function (event: { code: any }) {
        if (CanRun.inputting) return;
        switch (event.code) {
            case "ArrowUp":
            case "KeyW":
                moveForward = false;
                break;

            case "ArrowLeft":
            case "KeyA":
                moveLeft = false;
                break;

            case "ArrowDown":
            case "KeyS":
                moveBackward = false;
                break;

            case "ArrowRight":
            case "KeyD":
                moveRight = false;
                break;
            case "ShiftLeft":
            case "ShiftRIGHT":
                pressShift = false;
                break;
        }
        //都没有按下才设置false
        if (!moveForward && !moveBackward && !moveLeft && !moveRight) {
            isMoving = false;
        }
    };

    document.addEventListener("keydown", onKeyDown);
    document.addEventListener("keyup", onKeyUp);

    const removeEventListener = () => {
        document.removeEventListener("keydown", onKeyDown);
        document.removeEventListener("keyup", onKeyUp);
    };

    init.clearEventArr.push(removeEventListener);

    return { removeEventListener };
}

/**
 * 初始校园模型
 */
const initObjects = (complete?: VoidFunction) => {
    const addMonitoring = (monitoringArr: any) => {
        monitoringArr.monitoringChanges = (...items: any) => {
            objects.push(...items);
            window.render();
        };
    };

    const addScene = (planet: { withVolume: any; objects: THREE.Object3D }) => {
        addMonitoring(planet.withVolume);
        objects.push(...planet.withVolume);
        scene.add(planet.objects);
    };

    for (const planet of [ldMap]) {
        planet.init(() => {
            console.log("可以交互了");
            complete && complete();
            //只计算地图模型中的mesh 全局scene会把自身人物计算到
            // if (scene) nearCheck.calculationAllMeshWorldPosition(scene);
        });
        //进入宇宙的前提就是加载完默认星球的模型 直接设置为true
        // planet.webWorkerLoadModels && webWorkerParse(planet.webWorkerLoadModels);
        planet.setActerPositionOnDefaultStar();
        addScene(planet);
    }

    window.objects = objects;
};

/**
 * 初始化three场景 不能重复触发 会加载重复模型导致性能拉胯
 */
export const init = (parameters?: THREE.WebGLRendererParameters, complete?: VoidFunction) => {
    if (init.alreadyInit) return;
    init.alreadyInit = true;

    intGlobalVariable();
    initScene();
    initWebWorker();
    initObjects(complete);
    initControls();
    stats.init();
    initWebGLRenderer(parameters);
    window.render();
    renderControl.setRender(window.render);
    window.gui.add(
        {
            全部对象数量: () => {
                let count = 0;
                scene.traverse(() => count++);
                console.log("全部对象数量: ", count);
            },
        },
        "全部对象数量"
    );
};

init.alreadyInit = false;

/**
 * 回调清除事件监听数组
 */
init.clearEventArr = [() => clearChildren(scene as unknown as THREEMesh)] as Array<() => void>;

/**
 * 确定进入星球再渲染(监听) 在之前只加载数据
 */
init.enter = () => {
    // const keyPressListen = initKeyPressListen();

    window.dispatchEvent(new CustomEvent("startrender"));

    // init.mouseControls.lockMouse();
    window.render && window.render();
};

init.clear = () => {
    //清除监听和旧的模型
    console.log("清除缓存");
    cancelAnimationFrame(animationFrame);
    window.dispatchEvent(new CustomEvent("stoprender"));
    init.clearEventArr.forEach((event) => event());
};

const acterPosition = new THREE.Vector3();

/**
 * 锁定鼠标时逐帧执行的操作 -- 核心逻辑
 */
function animate() {
    animationFrame = requestAnimationFrame(animate);
    //计算是否需要计算陷入地下 比如 站在高处就不必计算是否会陷入地下
    stats.update();
    const time = performance.now(); //每一帧的间隔时间 保证相同时间移动相同距离
    const delta = (time - prevTime) / 1000;
    acterPosition.copy(acterWrap.position);
    otherUserList.updateAnimation();
    const nearbyObjects = nearCheck.calculateNearbyObjects();

    //根据boolean值 巧妙判断方向 决定下面移动的增量
    direction.z = Number(moveForward) - Number(moveBackward);
    direction.x = Number(moveRight) - Number(moveLeft);
    direction.normalize();

    //缓冲 慢慢把速度降下来 即停止移动还会向前滑一小段 数值慢慢都会归零
    velocity.x -= velocity.x * 10.0 * delta;
    velocity.z -= velocity.z * 10.0 * delta;
    velocity.y -= 5.8 * 100.0 * delta;
    //四个方位是否产生碰撞
    let leftCollide = 0;
    let rightCollide = 0;
    let forwardCollide = 0;
    let backCollide = 0;
    let uphillHeight = 0;

    if (!CanRun.inputting) {
        //碰撞检测
        if (moveForward) forwardCollide = directionCheck(controls, nearbyObjects, acterPosition, "forward").length;
        if (moveBackward) backCollide = directionCheck(controls, nearbyObjects, acterPosition, "back").length;
        if (moveLeft) leftCollide = directionCheck(controls, nearbyObjects, acterPosition, "left").length;
        if (moveRight) rightCollide = directionCheck(controls, nearbyObjects, acterPosition, "right").length;

        //如果不为0就表示按下了前或者后键
        if (direction.z) velocity.z -= direction.z * 400.0 * delta;
        if (direction.x) velocity.x -= direction.x * 400.0 * delta;
        //是否shift加速
        const quicken = pressShift ? magnification : 1;
        //未限制的移动距离
        rightDistance = -velocity.x * delta * quicken;
        forwardDistance = -velocity.z * delta * quicken;

        if (isMoving) {
            acterPlayer.start();
            const dir = dirType(moveForward, moveBackward, moveRight);
            const hdis = !!direction.z ? forwardDistance : rightDistance;
            [uphillHeight, hillAngle, isUphill] = uphillCheck(controls, dir, nearbyObjects, hdis);

            if (!enterKey && controls.moving) {
                enterKey = true;
                scaleToTarget(controls, acterWrap, cameraWrap);
            }
        } else {
            enterKey && (enterKey = false);
            controls.syncRotate && (controls.syncRotate = false);
            acterPlayer.stop();
        }
    }

    upAndFallCheck(nearbyObjects);

    if ((moveRight && rightCollide) || (moveLeft && leftCollide)) {
        rightDistance = isUphill ? rightDistance * hillAngle : 0;
    }
    if ((moveForward && forwardCollide) || (moveBackward && backCollide)) {
        // console.log("上坡", isUphill);
        forwardDistance = isUphill ? forwardDistance * hillAngle : 0;
    }

    if (direction.x) controls.moveRight(rightDistance);
    if (direction.z) controls.moveForward(forwardDistance);

    let upDistance = velocity.y * delta + uphillHeight;

    acterWrap.position.y += upDistance;
    cameraWrap.position.y += upDistance;

    //限制最低高度
    if (acterWrap.position.y < 0) {
        velocity.y = 0;
        acterWrap.position.y = 0;
        cameraWrap.position.y = 0;
        canJump = true;
    }

    /**
     * 是否有需要进入物体触发的条件
     */
    nearbyObjects.forEach((mesh) => {
        if (mesh.userData.nearbyCheck) {
            mesh.userData.nearbyCheck(acterPosition);
        }
    });

    animationControl.update();
    // currentPlanet.ticks();
    prevTime = time;
    window.render();
}

/**
 * 检测头上与脚下
 */
function upAndFallCheck(nearbyObjects: THREE.Object3D[]) {
    //不获取新的检测会出错待排查
    const acterPosition = acterWrap.position;
    const upCollideLength = upCollide(acterPosition, acterHeight, nearbyObjects).length;

    //如果头上有障碍并且在向上运动时 和下方检测一样 第一次反弹后第二帧还能检测到头上有物体 就导致一直在乘-1一直闪烁
    if (upCollideLength && velocity.y > 0) {
        velocity.y *= -1;
        console.log("碰头了");
        return;
    }

    //* 刚起跳时向下检测射线能检测到当前脚下的物体 所以会将canJump又变回true 实现二段跳
    //向上跳起时不用检测脚下
    if (velocity.y > 0) return;

    //脚下
    const fallMesh = fallCollide(acterPosition, nearbyObjects);
    const onObject = fallMesh[0];
    //如果下方没有物体不处理
    if (!onObject) return;
    //新的连续跳跃计数
    ejectorParams.recount();
    //上坡时不被缓慢下落干扰
    // if (isUphill && isMoving) return (velocity.y = 0);
    if (isUphill) return (velocity.y = 0);
    //射线的远端触碰到了物体就返回物体，如果碰到就停止下落就会造成浮空 因为射线没走完 在头就停下身子的高度就是浮空的高度
    //1 是允许误差范围即脚离下方物体的高度
    const dis = onObject.distance;
    if (dis > 1) {
        velocity.y = Math.max(-dis * 10, velocity.y);
    } else {
        velocity.y = Math.max(0, velocity.y);
    }

    canJump = true;

    /**
     * 处理踩在物体上发生的绑定在物体身上的事件
     */
    if (onObject.object.userData.allow) {
        onObject.object.userData.effect();
    }
}

/**
 * 外部程序控制人物跳跃
 */
export const acterJump = (JumpHeight: number) => {
    velocity.y += JumpHeight;
};
